 -- 2022-8-9

---[[ 字符串缓冲区

   -- 假设我们正在开发一段处理字符串的程序 比如逐行的读取一个文件 典型的代码可能形如
   local buff = ""
   for line in io.lines() do
      buff = buff .. line .. "\n"
   end
   -- 虽然这段Lua代码看似能够工作 但实际上在处理大文件时却可能导致巨大的性能开销
   -- 例如 在作者的新机器上读取一个4.5MB大小的文件需要超过30秒的时间

   -- 这是为什么呢 为了搞清楚到底发生了什么 让我们想象一下循环中发生了什么
   -- 假设每行有20字节 当我们读取大概2500行后 buff就会变成一个50KB大小的字符串
   -- 在Lua语言中进行字符串连接buff..line.."\n"时 会创建一个50020字节的新字符串 然后从buff中赋值50000字节中到这个新字符串中
   -- 这样 对于后续的每一行 Lua语言都需要移动大概50KB且还在不断增长的内存 因此该算法的时间复杂度是二次方的
   -- 在读取了100行(仅2KB)以后 Lua语言就已经移动了至少5MB内存
   -- 当Lua语言完成了350KB的读取后 它已经至少移动了50GB的数据
   -- (这个问题不是Lua语言特有的:在其他语言中 只要字符串是不可变值 就会出现类似的问题 其中最有名的例子是Java)

   -- 在继续学习前 我们必须说明 上述场景中的情况并不常见
   -- 对于较小的字符串 上述循环并没有什么问题
   -- 当读取整个文件时 Lua语言提供了带有参数的函数io.read("a")来一次性的读取整个文件 不过有时候我们必须面对这个问题
   -- Java提供了StringBuffer类来解决这个问题 而在Lua中 我们可以把一个表当作字符串缓冲区 其关键是使用函数table.concat
   -- 这个函数会将指定列表中的所有字符串连接起来并返回连接后的结果 使用concat函数可以重写上述循环
   local t = {}
   for line in io.lines() do
      t[#t+1] = line .. "\n"
   end
   local s = table.concat(t)
   -- 之前的代码读取同样的文件需要超过半分钟 而上述实现只需要不到0.05秒
   -- (不过尽管如此 读取整个文件最好还是使用带有参数"a"的io.read函数)

   -- 我们还可以做的更好 函数concat还有第二个可选参数 用于指定插在字符串间的分隔符
   -- 有了这个分隔符 我们就不必在每行后插入换行符了
   local t = {}
   for line in io.lines() do
      t[#t+1] = line
   end
   local s = table.concat(t,"\n")

   -- 虽然函数concat能够在字符串之间插入分隔符 但我们还需要增加最后一个换行符
   -- 最后一次字符串连接创建了结果字符串的一个副本 这个副本可能已经相当长了
   -- 虽然没有直接的选项能够让函数concat插入这个额外的分隔符 但我们可以想办法绕过 只需在字符串t后面添加一个空字符串就行了
   t[#t+1] = ""
   s = table.concat(t,"\n")
   -- 现在这样 正如我们期望的那样 会在最后一个字符串添加一个换行符 从而解决了问题 当然也会有其他解决方法 但效率可能不如此方法
--]]